<?php defined('BASEPATH') OR exit('No direct script access allowed');
require_once APPPATH.'eloquent_orm/core/Collection_iterator.php';
require_once BASEPATH.'database/DB.php';

class Collection extends Collection_iterator
{   
    /**
     * 转义字段
     * @var int
     */
    const ESCAPE = 1;
    
    /**
     * 不转义字段
     * @var int
     */
    const NO_ESCAPE = 2;
    
    /**
     * 自动转义字段
     * @var int
     */
    const ESCAPE_AUTO = 3;
    
    /**
     * 从Eloquent反射的db对象
     * @var object
     */
    private $_db = NULL;
    
    /**
     * 实例化此类的对象
     * @var object
     */
    private $_constructor = NULL;
    
    /**
     * Eloquent反射来的表名
     * @var string
     */
    protected $table = '';
    
    /**
     * Eloquent反射来的find_key
     * @var string
     */
    protected $find_key = '';
    
    /**
     * Eloquent反射来的时间戳状态
     * @var boolean
     */
    protected $timestamps = FALSE;
    
    /**
     * Eloquent反射来的主键
     * @var string
     */
    protected $primary_key = '';
    
    public function __construct($class_name, $obj = NULL)
    {
        parent::__construct();
        $this->_init($class_name, $obj);
    }
    
    /**
     * 外部使用db的查询构造器时, 调用此方法
     * @param string $method
     * @param array $params
     * @return Collection
     */
    public function __call($method, $params)
    {
        call_user_func_array(array($this->_db, $method), $params);
        return $this;
    }
    
    /**
     * 初始化操作<p>
     * 传入对象则导入对象, 不再生成, 否则生成新对象
     * @param string $class_name
     * @param object $obj
     */
    private function _init($class_name, $obj = NULL)
    {
        if ($this->_items == NULL)
        {
            if (empty($obj))
            {
                $this->_constructor = new $class_name();
            }
            else
            {
                $this->_constructor = $obj;
            }
            
            $this->_set_default_property($class_name);
        }
    }
    
    /**
     * 获取Eloquent中的属性, 设置成自身的默认属性
     * @param string $class_name
     */
    private function _set_default_property($class_name)
    {
        // 获取Eloquent构造者中受保护的属性
        $refps = Elo_reflection::get_properties($class_name, $this->_constructor, Elo_reflection::IS_PROTECTED);
        
        foreach ($refps as $key => $refp)
        {
            if ($key !== '_attributes' && $key !== '_collection' && $key !== '_class_name')
            {
                $this->$key = $refp;
            }
        }
    }
    
    /**
     * 将数据存入属性中
     * @param array $data
     */
    private function _save_attributes($data)
    {
        ! empty($data) && $this->_save_clone($data);
    }
    
    /**
     * 克隆Eloquent对象, 向这些对象中存入查询出来的数据作为属性
     * 并将这些对象加入自身, 成为Eloquent的聚合体
     * @param array $data
     */
    private function _save_clone($data)
    {
        $elo = $this->_constructor;
        
        foreach ($data as $k => $v)
        {
            $new_elo = clone $elo;
            
            foreach ($v as $key => $value)
            {
                $new_elo->$key = $value;
            }
            
            $this->_items[$k] = $new_elo;
        }
    }
    
    /**
     * select操作, 将主键字段查出
     * @param unknown $select
     * @return Collection
     */
    public function select($select)
    {
        $reg = '/,?\s*'.$this->table.'\.\*/';
        
        if (trim($select) !== '*' && ! preg_match($reg, $select))
        {
            $this->_db->select($this->table.'.'.$this->primary_key);
        }
        
        $this->_db->select($select);
        return $this;
    }
    
    /**
     * 获取所有数据
     * @return Ambigous <void, Collection>
     */
    public function all()
    {
        return $this->get();
    }
    
    /**
     * 根据find_key查找一条数据
     * @param int $id
     * @return Ambigous <void, multitype:>
     */
    public function find($id)
    {
        return $this->where($this->find_key, $id)->first();
    }
    
    /**
     * 根据find_key查找一条数据, 没找到则404
     * @param int $id
     * @return Ambigous
     */
    public function find_or_fail($id)
    {
        $elo = $this->find($id);
        
        if ( ! $elo->count())
        {
            show_404();
        }
        
        return $elo;
    }
    
    /**
     * 拼接where条件
     * @param mixed $field
     * @param string $value
     * @return Collection
     */
    public function where($field, $value = '')
    {
        if (is_array($field))
        {
            $this->_db->where($field);
        }
        else
        {
            $this->_db->where(array($field => $value));
        }
        
        return $this;
    }
    
    /**
     * where_in语句
     * @param string $field
     * @param mixed $range
     * @return Collection
     */
    public function where_in($field, $range)
    {
        if ( ! is_array($range))
        {
            $range = array($range);
        }
        
        $this->_db->where_in($field, $range);
        return $this;
    }
    
    /**
     * 获取所有数据
     * @return void|Collection
     */
    public function get()
    {
        $res = $this->_db->get($this->table)->result_array();
        
        if (empty($res))
        {
            return;
        }
        
        $this->_save_attributes($res);
        return $this;
    }
    
    /**
     * 统计数据的条数
     * @return number
     */
    public function count()
    {
        return count($this->_items);
    }
    
    /**
     * 返回第一条数据
     * @return void|multitype:
     */
    public function first()
    {
        $this->take(1);
        
        if ( ! $this->count())
        {
            return;
        }
        
        return $this->_items[0];
    }
    
    /**
     * 获取一定条数数据
     * @param number $num
     * @return void|Collection
     */
    public function take($num = 0)
    {
        if ( ! $this->count() && ! $this->get())
        {
            return;
        }
        
        $this->_items = array_slice($this->_items, 0, $num);
        return $this;
    }
    
    /**
     * 外部更新数据方法, 数据只支持数组形式
     * @param array $data 更新数据的数组形式
     * @param int $escape 是否转义, 默认自动转义[ESCAPE|NO_ESCAPE|ESCAPE_AUTO]
     * @param boolean $all_update 无条件更新, 默认不允许无条件更新
     * @return boolean
     */
    public function update(Array $data, $escape = self::ESCAPE_AUTO, $all_update = FALSE)
    {
        if ($all_update === FALSE)
        {
            $this->_check_where_ready();
        }
        
        if ($this->timestamps === TRUE)
        {
            $data['update_time'] = time();
        }
        
        if ($escape === self::ESCAPE)
        {
            $result = $this->_db->update($this->table, $data);
        }
        elseif ($escape === self::ESCAPE_AUTO)
        {
            // 不转义字符正则
            $no_escape_reg = '/^\s?[-+*\/]\s?\d+$/'; 
            
            foreach ($data as $field => $value)
            {
                $_escape = ! preg_match($no_escape_reg, ltrim($value, $field));
                $this->_db->set($field, $value, $_escape);
            }
        }
        elseif ($escape === self::NO_ESCAPE)
        {
            $_escape = TRUE;
            
            foreach ($data as $field => $value)
            {
                $this->_db->set($field, $value, $_escape);
            }
        }
        else
        {
            show_error('The parameters of the illegal');
        }
        
        return $this->_db->update($this->table);
    }
    
    /**
     * 获取指定索引的Eloquent对象
     * @param int $key
     * @return multitype:
     */
    public function get_index($key)
    {
        return $this->_items[$key];
    }
    
    /**
     * pivot对象保存时加上时间
     * @return Collection
     */
    public function with_timestamps()
    {
        foreach ($this->_items as $item)
        {
            $item->pivot->set_timestamps(TRUE);
        }
        
        return $this;
    }
    
    /**
     * 检查是否有where语句
     */
    private function _check_where_ready()
    {
        $where = Elo_reflection::get_property(get_class($this->_db), $this->_db, 'qb_where');
        empty($where) && show_error('No query conditions');
    }

    /**
     * 获取其他表数据
     * @param string $table_name
     */
    private function _get_other_table($table_name)
    {
        return $this->_db->get($table_name)->result_array();
    }
}